﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Packaging;
using System.Configuration;
using Microsoft.IT.Crm.Utility.CrmSolutionStringReplacement;
using Microsoft.Xrm.Sdk;

namespace Microsoft.IT.Crm.Utility.CrmSolutionManager
{
    public class SolutionExporter : ISolutionExporter
    {
        private ServiceInformation ServiceInformation;
        protected XrmServiceHelper XrmServiceHelper;

        public SolutionExporter()
        {
            ServiceInformation = GenerateServiceInformation();
            XrmServiceHelper = new XrmServiceHelper(ServiceInformation);
        }

        protected void ExportSolution(byte[] bytes, string zip)
        {
            if (!zip.Contains("/"))
                zip = Path.Combine(Path.Combine(Environment.CurrentDirectory, CRMSolutionManagerConfig.Instance.SolutionFolder), zip);

            if (File.Exists(zip))
                File.Delete(zip);

            var dir = Path.GetDirectoryName(zip);
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            File.WriteAllBytes(zip, bytes);
        }

        private ServiceInformation GenerateServiceInformation()
        {
            return new ServiceInformation();
        }

        public void Export(bool managed)
        {
            ClearSolutionFolder();

            var solutions = XrmEntityService.RetrieveMultipleEntity(EntityConstants.Const_Solution_Entity, EntityConstants.Static_Solution_Key_Fields,
                    EntityConstants.Const_Solution_Entity_UniqueName, CRMSolutionManagerConfig.Instance.SolutionFilter);
            foreach (var solution in solutions)
                try
                {
                    if (!solution.GetAttributeValue<bool>(EntityConstants.Const_Solution_Entity_IsManaged))
                    {
                        var uniqueName = solution.GetAttributeValue<string>(EntityConstants.Const_Solution_Entity_UniqueName);

                        EnterpriseLibLogger.Log(string.Format("Retrieve solution components: {0}", uniqueName));
                        var components = XrmSolutionService.RetrieveAllComponents(solution);
                        EnterpriseLibLogger.Log(string.Format("Retrieve solution web resources: {0}", uniqueName));
                        var webResources = GetSolutionWebResources(components);
                        EnterpriseLibLogger.Log(string.Format("Retrieve solution plugin steps: {0}", uniqueName));
                        var pluginSteps = GetSolutionPluginSteps(components);
                        EnterpriseLibLogger.Log(string.Format("String replace for the web resources and plugin steps: {0}", uniqueName));
                        BeforeExport(webResources, pluginSteps);

                        EnterpriseLibLogger.Log(string.Format("Start exporting solution: {0}", uniqueName));
                        var exportPackage = XrmSolutionService.ExportSolution(uniqueName, managed);
                        var exportName = GenerateExportSolutionName(solution);
                        ExportSolution(exportPackage, exportName);
                        EnterpriseLibLogger.Log(string.Format("End exporting solution: {0}", uniqueName));

                        EnterpriseLibLogger.Log(string.Format("String replace revert for the web resources and plugin steps: {0}", uniqueName));
                        AfterExport(webResources, pluginSteps);
                        EnterpriseLibLogger.Log(string.Format("Solution:{0} exported.", exportName));
                    }
                }
                catch (Exception e)
                {
                    EnterpriseLibLogger.Log(string.Format("Error: {0}", e.Message));
                }
        }

        /// <summary>
        /// get plugin steps from the plugin step entity according to the component object ids.
        /// </summary>
        /// <param name="components"></param>
        /// <returns></returns>
        private DataCollection<Entity> GetSolutionPluginSteps(DataCollection<Entity> components)
        {
            if (components == null) return null;

            var objectIds =
                from p in components
                where p.GetAttributeValue<OptionSetValue>(EntityConstants.Const_SolutionComponent_Entity_ComponentType).Value == EntityConstants.Const_ComponentType_SDKMessageProcessingStep
                select p.GetAttributeValue<Guid>(EntityConstants.Const_SolutionComponent_Entity_ObjectId).ToString();

            return XrmSolutionService.RetrieveAllPluginSteps(objectIds);
        }

        /// <summary>
        /// get web resources from the web resource entity according to the component object ids.
        /// </summary>
        /// <param name="components"></param>
        /// <returns></returns>
        private DataCollection<Entity> GetSolutionWebResources(DataCollection<Entity> components)
        {
            if (components == null) return null;

            var objectIds =
                from p in components
                where p.GetAttributeValue<OptionSetValue>(EntityConstants.Const_SolutionComponent_Entity_ComponentType).Value == EntityConstants.Const_ComponentType_WebResource
                select p.GetAttributeValue<Guid>(EntityConstants.Const_SolutionComponent_Entity_ObjectId).ToString();
            return XrmSolutionService.RetrieveAllWebResources(objectIds);
        }

        /// <summary>
        /// revert the changes to the dev environment string.
        /// </summary>
        /// <param name="webResources"></param>
        /// <param name="pluginSteps"></param>
        private void AfterExport(ICollection<Entity> webResources, ICollection<Entity> pluginSteps)
        {
            var section = (SolutionStringReplacementSection)ConfigurationManager.GetSection("SolutionStringReplacement");
            if (section != null)
                foreach (SolutionString item in section.StringCollection)
                {
                    if (webResources != null)
                        ReplaceStringForWebResources(webResources, item.NewValue, item.OldValue);

                    if (pluginSteps != null)
                        ReplaceStringForPluginSteps(pluginSteps, item.NewValue, item.OldValue);
                }
            XrmSolutionService.PublishSolution(webResources);
        }

        /// <summary>
        /// replace the string values according to different environment settings.
        /// </summary>
        /// <param name="webResources"></param>
        /// <param name="pluginSteps"></param>
        private void BeforeExport(ICollection<Entity> webResources, ICollection<Entity> pluginSteps)
        {
            var section = (SolutionStringReplacementSection)ConfigurationManager.GetSection("SolutionStringReplacement");
            if (section != null)
                foreach (SolutionString item in section.StringCollection)
                {
                    if (webResources != null)
                        ReplaceStringForWebResources(webResources, item.OldValue, item.NewValue);

                    if (pluginSteps != null)
                        ReplaceStringForPluginSteps(pluginSteps, item.OldValue, item.NewValue);
                }
            XrmSolutionService.PublishSolution(webResources);
        }

        /// <summary>
        /// replace the hard-coded string values in the web resource java scripts.
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        private void ReplaceStringForWebResources(IEnumerable<Entity> collection, string oldValue, string newValue)
        {
            if (collection != null)
                foreach (var item in collection)
                {
                    var oldContent = item.GetAttributeValue<string>(EntityConstants.Const_WebResource_Entity_Content);
                    oldContent = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(oldContent));

                    var newContent = oldContent.Replace(oldValue, newValue);
                    if (newContent != oldContent)
                    {
                        item.Attributes[EntityConstants.Const_WebResource_Entity_Content] = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(newContent));
                        XrmEntityService.UpdateEntity(item);
                    }
                }
        }

        /// <summary>
        /// replace the string values in the plugin step's configuration.
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="oldValue"></param>
        /// <param name="newValue"></param>
        private void ReplaceStringForPluginSteps(IEnumerable<Entity> collection, string oldValue, string newValue)
        {
            if (collection != null)
                foreach (var item in collection)
                {
                    var oldContent = item.GetAttributeValue<string>(EntityConstants.Const_SdkMessageProcessingStep_Entity_Configuration);

                    var newContent = oldContent.Replace(oldValue, newValue);
                    if (newContent != oldContent)
                    {
                        item.Attributes[EntityConstants.Const_SdkMessageProcessingStep_Entity_Configuration] = newContent;
                        XrmEntityService.UpdateEntity(item);
                    }
                }
        }

        /// <summary>
        /// clear the solution folder, and delete all the old solutions.
        /// </summary>
        private void ClearSolutionFolder()
        {
            if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, CRMSolutionManagerConfig.Instance.SolutionFolder)))
            {
                var files = Directory.GetFiles(Path.Combine(Environment.CurrentDirectory, CRMSolutionManagerConfig.Instance.SolutionFolder));
                foreach (var file in files)
                {
                    File.Delete(file);
                }
            }
        }

        internal string GenerateExportSolutionName(Xrm.Sdk.Entity solution)
        {
            if (solution == null) return string.Empty;

            var uniqueName = solution.GetAttributeValue<string>(EntityConstants.Const_Solution_Entity_UniqueName);
            var versions = solution.GetAttributeValue<string>(EntityConstants.Const_Solution_Entity_Version).Split('.');
            var managed = solution.GetAttributeValue<bool>(EntityConstants.Const_Solution_Entity_IsManaged);

            var major = versions.Length > 0 ? versions[0] : "0";
            var minor = versions.Length > 1 ? versions[1] : "0";
            var build = versions.Length > 2 ? versions[2] : "0";
            var revision = versions.Length > 3 ? versions[3] : "0";

            return string.Format("{0}_{1}_{2}_{3}_{4}.zip", uniqueName, major, minor, build, revision);
        }
    }
}
